package de.westranger.forex.trading.genetic;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import de.westranger.forex.trading.genetic.allel.operator.TerminalSymbol;

public final class CSVBasedContentResolver implements ContentResolver {
	private final double[][] data;

	private CSVBasedContentResolver(final double[][] data){
		this.data = data;
	}
	
	public CSVBasedContentResolver(final String absolutePath)
			throws IOException, ParseException {

		if(absolutePath.endsWith(".bin.gz")){
			this.data = this.readBinaryCSV(absolutePath);
		}else{
			String[][] tmpFile = null;
			if(absolutePath.endsWith(".gz")){
				tmpFile = readGzipCSV(absolutePath);
			}else{
				tmpFile = readCSV(absolutePath);
			}
			final Stack<TerminalSymbol> headings = new Stack<TerminalSymbol>();

			for (String head : tmpFile[0]) {
				TerminalSymbol found = null;
				for (TerminalSymbol ts : TerminalSymbol.values()) {
					if (head.equalsIgnoreCase(ts.name())) {
						found = ts;
						headings.add(ts);
						break;
					}
				}
				if (found == null) {
					throw new ParseException("the String " + head
							+ "cound be matched with a TerminalSymbol", 0);
				}
			}

			this.data = new double[TerminalSymbol.values().length][tmpFile.length-1];

			String[] line;
			for (int i = 0; i < tmpFile.length-1; i++) {
				line = tmpFile[i+1];
				for (int j = 0; j < line.length; j++) {
					this.data[headings.elementAt(j).getValue()][i] = Double
							.parseDouble(line[j]);
				}
			}			
		}
	}

	private String[][] readCSV(final String absolutePath) throws IOException, ParseException {
		final List<String[]> tmpFile = new LinkedList<String[]>();
		final BufferedReader bfrd = new BufferedReader(new FileReader(
				absolutePath));

		if (bfrd.ready()) {
			tmpFile.add(lineToStack(bfrd.readLine()));
		}
		
		String[] tmpList;
		int lineCounter = 0;
		while (bfrd.ready()) {
			tmpList = lineToStack(bfrd.readLine());
			if (tmpFile.get(tmpFile.size() - 1).length != tmpList.length) {
				bfrd.close();
				throw new ParseException(
						" number of items per line ("+lineCounter+") are not consitent should be "
								+ tmpFile.get(tmpFile.size() - 1).length
								+ " but found " + tmpList.length, 0);
			}
			tmpFile.add(tmpList);
			lineCounter++;
		}
		bfrd.close();
		
		final String[][] result = new String[tmpFile.size()][];
		tmpFile.toArray(result);
		return result.clone();
	}

	private String[][] readGzipCSV(final String absolutePath) throws IOException, ParseException {
		final List<String[]> tmpFile = new LinkedList<String[]>();
		final BufferedReader bfrd = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(absolutePath))));
		
		if (bfrd.ready()) {
			tmpFile.add(lineToStack(bfrd.readLine()));
		}
		
		//int lineCount = 0;
		String[] tmpList;
		while (bfrd.ready()) {
			tmpList = lineToStack(bfrd.readLine());
			if (tmpFile.get(tmpFile.size() - 1).length != tmpList.length) {
				bfrd.close();
				throw new ParseException(
						" number of items per are not consitent should be "
								+ tmpFile.get(tmpFile.size() - 1).length
								+ " but found " + tmpList.length, 0);
			}
			tmpFile.add(tmpList);
			//lineCount++;
		}
		bfrd.close();
		
		//System.out.println("line count + "+ lineCount);
		//System.out.println("line count + "+ tmpFile.size());
		
		
		final String[][] result = new String[tmpFile.size()][];
		tmpFile.toArray(result);
		
		//System.out.println("line count + "+ result.length);
		
		return result.clone();
	}
	
	private double[][] readBinaryCSV(final String absolutePath) throws FileNotFoundException, IOException, ParseException {
		final DataInputStream dis = new DataInputStream(new GZIPInputStream(new FileInputStream(absolutePath)));
		final int numHeadings = dis.readInt();
		final int numLines = dis.readInt();
		final double[][] tmpData = new double[TerminalSymbol.values().length][numLines];
		final Stack<TerminalSymbol> headings = new Stack<TerminalSymbol>();
		
		for(int i=0;i<numHeadings;i++){
			headings.push(TerminalSymbol.parse(dis.readInt()));
		}
		
		// now after redings all headings we can read all data
		for(int i=0;i<numLines;i++){
			for(TerminalSymbol ts:headings){
				tmpData[ts.getValue()][i] = dis.readDouble();
			}			
		}

		dis.close();
		return tmpData;
	}

	public void writeBinaryCSV(final String absolutePath) throws FileNotFoundException, IOException {
		final DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(absolutePath))));
		
		final Stack<TerminalSymbol> headings = new Stack<TerminalSymbol>();
		for (TerminalSymbol ts : TerminalSymbol.values()) {
			if(ts != TerminalSymbol.DO_NOT_USE_ME){
				double sum = 0.0;
				
				for(double d:data[ts.getValue()]){
					sum += d;
				}
				
				if(sum != 0.0){
					headings.push(ts);
				}
			}
		}

		// first we write the line length
		dos.writeInt(headings.size());
		
		// afterward we write the numer ob lines
		dos.writeInt(this.getValueCount());
		
		// now we write all heading values
		for(TerminalSymbol ts:headings){
			dos.writeInt(ts.getValue());
		}
		
		// now we write the data
		for(int i=0;i<this.getValueCount();i++){
			for(TerminalSymbol ts:headings){
				dos.writeDouble(data[ts.getValue()][i]);
			}
		}
		dos.close();
	}
	
	private String[] lineToStack(final String line) {
		final LinkedList<String> tmpLine = new LinkedList<String>();
		final StringTokenizer strTk = new StringTokenizer(line, ";");
		while (strTk.hasMoreTokens()) {
			tmpLine.add(strTk.nextToken());
		}
		final String[] result = new String[tmpLine.size()];
		tmpLine.toArray(result);
		return result;
	}

	public String csv2gzip(final String path){
		GZIPOutputStream out;
		String newPath = null;
		try {
			newPath = path+".gz";
			out = new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(newPath)));
			BufferedInputStream br = new BufferedInputStream(new FileInputStream(path));
			
			while(br.available() > 0){
				out.write(br.read());
			}
			br.close();
	        out.flush();
	        out.finish();
	        out.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return newPath;
	}
	
	
	@Override
	public double resolve(final int termSymb, final int valuePosition) {
		return this.data[termSymb][valuePosition];
	}

	@Override
	public int getValueCount() {
		return this.data[0].length;
	}
	
	@Override
	public ContentResolver clone(){
		return new CSVBasedContentResolver(this.data.clone());
	}
}
